/*
 * server_test.cpp
 *
 *  Created on: Jun 7, 2012
 *      Author: ariel
 */

#include "protocol.h"

#include <iostream>
#include <sys/socket.h>
#include <errno.h>
#include <cstdio>
#include <iostream>
#include <netinet/in.h>
#include <cstring>
#include <arpa/inet.h>
#include <cstdlib>
#include <cassert>
#include <string>
#include <pthread.h>

using namespace std;

#define INVALID_SOCKET (-1)
#define NUM_CLIENTS (10)

pthread_t threads[NUM_CLIENTS];
int client_socket[NUM_CLIENTS];

/////////////////////////////////////////
// 		send message to the server
////////////////////////////////////////
void send_message(int client_socket, messages_type_t type, const char *payload)
{
	out_msg_t *msg;
	std::string my (payload);
	uint msg_length;

	if ( my.length() )
	{
		msg_length = MSG_METADATA_LENGTH + my.length() + 1;
	}
	else
	{
		msg_length = 0;
	}

	msg = (out_msg_t *) malloc ( msg_length );
	if ( NULL == msg )
	{
		cerr << "couldn't allocate buffer" << endl;
		return;
	}

	msg->header.type = (messages_type_t)htonl(type);
	msg->header.payload_length = htonl(my.length() + 1 );
	memcpy( & msg->payload, my.c_str(), my.length() );

	if( send( client_socket, msg , msg_length, 0) == -1)
	{
		perror("Error send data: ");
	}
	free(msg);
}

/////////////////////////////////////////
// 		get message from the server
////////////////////////////////////////
void get_message( int client_socket )
{
	uint bytecount;
	uint data_length;

	in_msg_t message;


	// get the header
	if( -1 == (bytecount = recv( client_socket, & message, MSG_METADATA_LENGTH, 0) ) )
	{
		perror("Error receiving data: ");
		return;
	}

	data_length = ntohl( message.header.payload_length );

	cout<< "New message: type: " << ntohl( message.header.type ) << "\tstatus: "
			<< ntohl( message.header.status ) << "\tlength: " << ntohl( message.header.payload_length );

	if ( data_length )
	{
		message.payload = new char[data_length];
		if ( NULL == message.payload )
		{
			cerr << "couldn't allocate buffer" << endl;
			return;
		}
		// get the buffer
		if( -1 == (bytecount = recv( client_socket, message.payload, data_length, 0) ) )
		{
			perror("Error receiving data: ");
			return;
		}

		cout << "\tbuffer: " <<  message.payload << endl;

		delete [] message.payload;
	}
	else
	{
		cout << endl;
	}
}

void thread0_func()
{
	sleep(8);
	cout<< "in thread0 func" << endl;

	//send_message( client_socket[0], MESSAGE_TYPE_TWIT, "This twit sent to you by thread0" );

}

void thread1_func()
{
	sleep(8);
	cout<< "in thread1 func" << endl;
}

void thread2_func()
{
	sleep(1);
	cout<< "in thread2 func" << endl;

	send_message( client_socket[2], MESSAGE_TYPE_WHO, "" );

	get_message( client_socket[2] );
}

void thread3_func()
{
	sleep(2);
	cout<< "in thread3 func" << endl;

	send_message( client_socket[3], MESSAGE_TYPE_FOLLOW, "client0_name" );

	get_message( client_socket[3] );
}



typedef void (*func) (void);

func threads_func[] =
{
		thread0_func,
		thread1_func,
		thread2_func,
		thread3_func
};

using namespace std;


void close_socket( int socket )
{
	out_msg_t *msg;
	string buffer;
	uint msg_size = MSG_METADATA_LENGTH;

	///////////////////////////////////////////////
	// 	send disconnect message to the server	//
	/////////////////////////////////////////////
	msg = (out_msg_t *) malloc (msg_size);
	if ( NULL == msg )
	{
		cerr << "couldn't allocate buffer" << endl;
		return;
	}

	msg->header.type = (messages_type_t)htonl(MESSAGE_TYPE_DISCONNECT);
	msg->header.payload_length = htonl( 0 );

	if( send( socket, msg , msg_size, 0) == -1)
	{
		perror("Error send data: ");
	}
	free(msg);
}

int start_client( string name )
{

	int host_port = 20000;
	const char* host_name = "127.0.0.1";

	int sock;
	struct sockaddr_in my_addr;
	uint bytecount;
	int yes = 1;

	uint data_length;
	uint msg_length;

	in_msg_t message;

	out_msg_t *msg;

	message.payload = NULL;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if( -1 == sock )
	{
		perror("Error initializing socket: ");
		exit(1);
	}

	if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, & yes, sizeof(int) ) == -1  )
	{
		perror("Error setsockopt() : ");
		exit(1);
	}

	my_addr.sin_family = AF_INET ;
	my_addr.sin_port = htons(host_port);

	memset( my_addr.sin_zero, 0, 8);
	my_addr.sin_addr.s_addr = inet_addr( host_name );

	if( connect( sock, (struct sockaddr*) & my_addr, sizeof(my_addr)) == -1 )
	{
		if( EINPROGRESS != errno )
		{
			perror("Error connecting socket");
			exit(1);
		}
	}

	/////////////////////////////////////////
	// 		send Connect message to the server
	////////////////////////////////////////
	data_length = name.length() + 1;
	msg_length = MSG_METADATA_LENGTH + data_length;
	msg = (out_msg_t *) malloc (msg_length);
	if ( NULL == msg )
	{
		cerr << "couldn't allocate buffer" << endl;
		exit(1);
	}

	msg->header.type = (messages_type_t)htonl(MESSAGE_TYPE_CONNECT);
	msg->header.payload_length = htonl( data_length );
	memcpy( & msg->payload, name.c_str(), name.length() + 1 );
	if( send( sock, msg , msg_length, 0 ) == -1)
	{
		perror("Error send data: ");
	}
	free(msg);

	///////////////////////////////////////////////////////
	// 		get confirmation message from the server	//
	/////////////////////////////////////////////////////
	// get the header
	if( -1 == (bytecount = recv( sock, & message, MSG_METADATA_LENGTH, 0) ) )
	{
		perror("Error receiving data: ");
		exit(1);
	}
	data_length = ntohl( message.header.payload_length );
	assert( ! data_length );

	if ( RESPONSE_STATUS_SUCCESS != ntohl(message.header.status)  )
	{
		cerr << "connection for " << name << " returned respone status != RESPONSE_STATUS_SUCCESS" << endl;
		pthread_exit(NULL);
	}


	return sock;
}

void* run(void *args)
{
	uint id = *((uint *)args);

	char id_num[2] = { '0' + id, '\0' };
	string name("client");
	name.append(id_num);
	name.append("_name");

	client_socket[id] = start_client( name );

	threads_func[id]();

	close_socket( client_socket[id] );

	return NULL;
}


int main()
{
	uint ids[NUM_CLIENTS];

	for ( uint i = 0; i < 4; ++i )
	{
		ids[i] = i;
		pthread_create( & threads[i] , NULL, run, & ids[i] );
	}


	for ( uint i = 0; i < 4; ++i )
	{
		pthread_join( threads[i], NULL );
	}


	return 0;

}
